﻿using System;
using System.Data.Entity;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using GroceryPriceBook.Core.Mappers.Extensions;
using GroceryPriceBook.Model;
using PurchaseLog = GroceryPriceBook.Core.ViewModel.PurchaseLog;

namespace GroceryPriceBook.Core.Service
{
	public class PurchaseLogHandler : BaseHandler<PurchaseLog>
	{
		public PurchaseLogHandler()
		{
			ServiceResult = ServiceResultEnum.Failure;
		}

		/// <summary>
		/// Add new PurchaseLog to the database
		/// </summary>
		/// <param name="obj">PurchaseLog object to be saved to the database</param>
		/// <returns>success or failure</returns>
		public override ServiceResultEnum Create(ref PurchaseLog obj)
		{
			// Number of changes as a result of the database change
			NumberChanges = 0;
			try
			{
				// Perform data access using the context
				using (var context = new PriceBookEntities())
				{
					for (var count = 0; count < obj.PurchaseLogItems.Count; count++)
					{
						obj.PurchaseLogItems[count].Id = count + 1;
					}

					// convert to database object
					var dbObj = obj.ToModel();

					// add to the database and retrieve the updated object back (namely the GUID generated into the Id)
					dbObj = context.PurchaseLogs.Add(dbObj);

					// commit changes to the database
					NumberChanges = context.SaveChanges();

					// convert the database object back to a presentation object with included changes from the database (if any)
					obj = dbObj.ToViewModel();
				}
			}
			catch (System.InvalidOperationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (ValidationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.Message);
			}

			return NumberChanges > 0 ? ServiceResultEnum.Success : ServiceResultEnum.Failure;
		}

		/// <summary>
		/// Update the provided PurchaseLog object in the database
		/// </summary>
		/// <param name="obj">PurchaseLog object to be updated in the database</param>
		/// <returns>success or failure</returns>
		public override ServiceResultEnum Update(ref PurchaseLog obj)
		{
			// Number of changes as a result of the database change
			NumberChanges = 0;
			try
			{
				// Perform data access using the context
				using (var context = new PriceBookEntities())
				{
					// convert to database object
					var dbObj = obj.ToModel();

					// update sub items that already existsed in the database
					dbObj.PurchaseLogItems.Where(item => item.Id != -1).ToList().ForEach(item => context.Entry(item).State = EntityState.Modified);

					// mark new sub items to be added to the database and give them a unique id
					var newCount = dbObj.PurchaseLogItems.Count;
					for (var count = 0; count < dbObj.PurchaseLogItems.Count; count++)
					{
						if (dbObj.PurchaseLogItems.ToList()[count].Id == -1)
						{
							dbObj.PurchaseLogItems.ToList()[count].Id = newCount++;
							dbObj.PurchaseLogItems.ToList()[count].PurchaseId = obj.Id;
							context.PurchaseLogItems.Add(dbObj.PurchaseLogItems.ToList()[count]);
						}
					}

					// add to the database and retrieve the updated object back (namely the GUID generated into the Id)
					context.Entry(dbObj).State = EntityState.Modified;

					// commit changes to the database
					NumberChanges = context.SaveChanges();

					// convert the database object back to a presentation object with included changes from the database (if any)
					obj = dbObj.ToViewModel();
				}
			}
			catch (System.InvalidOperationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (ValidationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.Message);
			}

			return NumberChanges > 0 ? ServiceResultEnum.Success : ServiceResultEnum.Failure;
		}

		/// <summary>
		/// Delete one PurchaseLog from the database - will error if it is FK'ed to another object
		/// </summary>
		/// <param name="id">id of the PurchaseLog to delete</param>
		/// <returns>success or failure status</returns>
		public override ServiceResultEnum Delete(Guid id)
		{
			// Number of changes as a result of the database change
			NumberChanges = 0;
			try
			{
				// Perform data access using the context
				using (var context = new PriceBookEntities())
				{
					// convert to database object
					var dbObj = context.PurchaseLogs.Find(id);

					if (dbObj != null)
					{
						// delete subitems first
						dbObj.PurchaseLogItems.ToList().ForEach(item => context.PurchaseLogItems.Remove(item));

						context.PurchaseLogs.Remove(dbObj);

						// commit changes to the database
						NumberChanges = context.SaveChanges();
					}
				}
			}
			catch (System.InvalidOperationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (ValidationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.Message);
			}

			return NumberChanges > 0 ? ServiceResultEnum.Success : ServiceResultEnum.Failure;
		}

		/// <summary>
		/// Retrieve all PurchaseLogs from the database for presentation
		/// </summary>
		/// <returns>list of PurchaseLogs</returns>
		public override List<PurchaseLog> ReadAll()
		{
			var objList = new List<PurchaseLog>();
			try
			{
				// Perform data access using the context
				using (var context = new PriceBookEntities())
				{
					// convert to presentation object
					objList = context.PurchaseLogs.ToList().ToViewModel();
				}
			}
			catch (System.InvalidOperationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.Message);
			}

			return objList;
		}

		/// <summary>
		/// Retrieve on PurchaseLog object for presentation
		/// </summary>
		/// <param name="id">id to look up the PurchaseLog in the database</param>
		/// <returns>presentation PurchaseLog object or null if not found</returns>
		public override PurchaseLog ReadOne(Guid id)
		{
			var obj = new PurchaseLog();
			try
			{
				// Perform data access using the context
				using (var context = new PriceBookEntities())
				{
					// convert to presentation object
					obj = context.PurchaseLogs.Find(id).ToViewModel();
				}
			}
			catch (System.InvalidOperationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.Message);
			}

			return obj;
		}

		/// <summary>
		/// Retrieve list of presentation objects filtered by provided object's properties
		/// </summary>
		/// <param name="obj">Presentation object with properties used to filter database query</param>
		/// <returns>List of PurchaseLog objects filtered and then sorted by name</returns>
		public override List<PurchaseLog> ReadFiltered(PurchaseLog obj)
		{
			var objList = new List<PurchaseLog>();
			try
			{
				// Build dynamic query based on the provided presentation object's properties
				// http://stackoverflow.com/questions/13628748/linq-dynamic-query-for-entity-framework
				var conditions = new List<Func<Model.PurchaseLog, bool>>();
				if (obj.TotalCost >= 0) { conditions.Add(x => x.TotalCost >= obj.TotalCost); }
				if (!string.IsNullOrEmpty(obj.Note)) { conditions.Add(x => x.Note.Contains(obj.Note)); }
				if (Guid.Empty != obj.StoreId) conditions.Add(x => x.Store == obj.StoreId);
				if (obj.PurchaseDate != DateTime.MinValue) { conditions.Add(x => x.PurchaseDate >= obj.PurchaseDate); }

				// Perform data access using the context
				using (var context = new PriceBookEntities())
				{
					var query = context.PurchaseLogs.AsQueryable();
					query = conditions.Aggregate(query, (current, condition) => current.Where(condition).AsQueryable());

					// convert to presentation object
					objList = query.OrderByDescending(a => a.PurchaseDate).ToList().ToViewModel();
				}
			}
			catch (System.InvalidOperationException ex)
			{
				Trace.WriteLine(ex.Message);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.Message);
			}

			return objList;
		}
	}
}
